
/***************************************************************************

Copyright (c) BizRulX. All rights reserved.

***************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Xml.XPath;


namespace RuleXSoft.RulXCodeGenerator
{
    // In order to be compatible with this single file generator, the input file has to
    // follow the schema in XMLClassGeneratorSchema.xsd

    /// <summary>
    /// Generates source code based on a XML document
    /// </summary>
    public static class SourceCodeGenerator
    {
        /// <summary>
        /// Create a CodeCompileUnit based on the XmlDocument doc
        /// In order to be compatible with this single file generator, the input XmlDocument has to
        /// follow the schema in XMLClassGeneratorSchema.xsd
        /// </summary>
        /// <param name="doc">An XML document that contains the description of the code to be generated</param>
        /// <param name="namespaceName">If the root node of doc does not have a namespace attribute, use this instead</param>
        /// <returns>The generated CodeCompileUnit</returns>
        public static CodeCompileUnit CreateCodeCompileUnit(XmlDocument doc, string namespaceName)
        {
            XmlElement root = doc.DocumentElement;

            if (root.Name != "Types")
            {
                throw new ArgumentException(string.Format(Strings.InvalidRoot, root.Name));
            }

            if (root.ChildNodes.Count == 0)
            {
                throw new ArgumentException(Strings.NoTypes);
            }

            //if (root.Attributes.GetNamedItem("namespace") != null)
            //{
            //    namespaceName = root.Attributes.GetNamedItem("namespace").Value;
            //}
            
            CodeCompileUnit code = new CodeCompileUnit();
            
            // Just for VB.NET:
            // Option Strict On (controls whether implicit type conversions are allowed)
            code.UserData.Add("AllowLateBound", false);
            // Option Explicit On (controls whether variable declarations are required)
            code.UserData.Add("RequireVariableDeclaration", true);

            CodeNamespace codeNamespace = new CodeNamespace(namespaceName);
            //Include the required namespaces
            codeNamespace.Imports.Add(new CodeNamespaceImport("System"));
            codeNamespace.Imports.Add(new CodeNamespaceImport("System.Collections"));
            codeNamespace.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
            codeNamespace.Imports.Add(new CodeNamespaceImport("RuleXSoft.FrameworkLib"));

            //Procedure to perse the XML document and find appropriate element / attribute in order to build the Attribute class
            //Find the Class name

            string rulXClassName = string.Empty;
            //rulXClassName = nodeWithClassName.Current.GetAttribute("name", string.Empty);

            rulXClassName = doc.SelectSingleNode("/Types/RuleSetClass/@name").Value;

            XPathNavigator navigatorForFields = doc.CreateNavigator();
            XPathNodeIterator nodeWithFields = navigatorForFields.Select("Types/RuleSetClass/Declaration/Fields");

            IList<string> listRulXObjectFields = new List<string>();

            XmlNodeList fieldNodeList;
            //fieldNodeList = doc.SelectNodes("Types/RuleSetClass/Declaration/Fields/Field/@name");

            fieldNodeList = doc.SelectNodes("Types/RuleSetClass/Declaration/Fields/Field");

            foreach (XmlNode nodeItem in fieldNodeList)
            {
                string strName = nodeItem.Attributes["name"].InnerText;
                string strDataType = nodeItem.Attributes["type"].InnerText;
                string strDirection = nodeItem.Attributes["direction"].InnerText;

                listRulXObjectFields.Add(strName + "|" + strDataType + "|" + strDirection);
            }

            

            codeNamespace.Types.Add(CreateClass(rulXClassName, listRulXObjectFields));
            


            code.Namespaces.Add(codeNamespace);
            return code;
        }

        

        private static CodeTypeDeclaration CreateClass(string strClassName, IList<string> nodeList)//(XmlNode node)
        {
            string className;
            string classAccess = "internal";
            bool isPartial;

            EnvDTE.Project project = VSEnvUtility.GetSelectedProjectHierarchyInstance();

            int indexOfDot = 0;
            indexOfDot = project.FullName.IndexOf('.');
            string SelectedProjectType = project.FullName.Substring(indexOfDot);

            //GetClassInfo(node, out className, out classAccess, out isPartial);

            CodeTypeDeclaration typeDeclaration = new CodeTypeDeclaration(strClassName);

            TypeAttributes attrib;

            switch (classAccess)
            {
                case "public":
                    attrib = TypeAttributes.Public;
                    break;
                case "internal":
                    attrib = TypeAttributes.NotPublic;
                    break;
                default:
                    throw new ArgumentException(string.Format(Strings.BadTypeAccess, classAccess));
            }

            typeDeclaration.IsPartial = true;// isPartial;
            typeDeclaration.IsClass = true;
            typeDeclaration.TypeAttributes = attrib | TypeAttributes.Class;

            if (SelectedProjectType.Equals(".csproj"))
            {
                typeDeclaration.BaseTypes.Add(new CodeTypeReference("ValidationAttributes<" + strClassName + ">"));
            }
            else if (SelectedProjectType.Equals(".vbproj"))
            {
                typeDeclaration.BaseTypes.Add(new CodeTypeReference("ValidationAttributes(Of " + strClassName + ")"));
            }
            else if (SelectedProjectType.Equals(".jsproj"))
            {
                typeDeclaration.BaseTypes.Add(new CodeTypeReference("ValidationAttributes<" + strClassName + ">"));
            }

            //Implementing from IComparer interfaces
            typeDeclaration.BaseTypes.Add(new CodeTypeReference("IComparer"));

            //CodeTypeReference baseType = new CodeTypeReference(typeof(System.Collections.Generic.IEnumerator<>));
            //baseType.TypeArguments.Add(new CodeTypeReference("ValidationAttributes", CodeTypeReferenceOptions.GenericTypeParameter));

            // Declare the constructor
            CodeConstructor constructor = new CodeConstructor();
            constructor.Attributes =
                MemberAttributes.Public | MemberAttributes.Final;

            foreach (string item in nodeList)
            {
                string strFieldType = string.Empty;
                string strFieldName = string.Empty;
                bool directionPath = false;

                IList<string> listFinal = item.Split('|');

                Type type;
                if (listFinal[1].Equals("string"))
                {
                    type = typeof(string);
                }
                else if (listFinal[1].Equals("int"))
                {
                    type = typeof(int);
                }
                else if (listFinal[1].Equals("double"))
                {
                    type = typeof(double);
                }
                else if (listFinal[1].Equals("datetime"))
                {
                    type = typeof(DateTime);
                }
                else if (listFinal[1].Equals("boolean"))
                {
                    type = typeof(bool?);
                }
                else if (listFinal[1].Equals("List<object>"))
                {
                    type = typeof(List<object>);
                }
                else
                {
                    type = typeof(string);
                }

                if (listFinal[2].ToUpper().Equals("OUTPUT"))
                {
                    directionPath = true;
                }
                else
                {
                    directionPath = false;
                }

                strFieldType = "string";
                strFieldName = listFinal[0];

                CodeMemberField field = CreateField(type, "_f" + strFieldName);
                typeDeclaration.Members.Add(field);

                CodeMemberProperty property = CreateProperties(strFieldName, "_f" + strFieldName, type, directionPath);
                typeDeclaration.Members.Add(property);

                
            }

            CodeMemberMethod method = CreateMethod(nodeList, strClassName, SelectedProjectType);
            typeDeclaration.Members.Add(method);

            return typeDeclaration;
        }

        private static void GetClassInfo(XmlNode node, out string className, out string classAccess, out bool isPartial)
        {
            if (node.Attributes != null && node.Attributes.GetNamedItem("name") != null && node.Attributes.GetNamedItem("name").Value != string.Empty)
            {
                className = node.Attributes.GetNamedItem("name").Value;
            }
            else
            {
                throw new ArgumentException(Strings.ClassNodeNoName);
            }

            if (node.Attributes.GetNamedItem("access") != null)
            {
                classAccess = node.Attributes.GetNamedItem("access").Value;
            }
            else
            {
                classAccess = "public";
            }

            if (node.Attributes.GetNamedItem("partial") != null)
            {
                isPartial = bool.Parse(node.Attributes.GetNamedItem("partial").Value);
            }
            else
            {
                isPartial = false;
            }
        }

        private static CodeMemberField CreateField(Type fieldType, string strFieldName)
        {
            string fieldName = strFieldName;
            string fieldAccess = "private";
            string fieldValue;
            //string fieldType = strFieldType;
            bool fieldIsStatic;

            //GetFieldInfo(n, out fieldName, out fieldAccess, out fieldValue, out fieldType, out fieldIsStatic);

            CodeMemberField field = new CodeMemberField(fieldType, fieldName);

            //if (n.Name == "Constant")
            //{
            //    // Set the correct attributes for a constant field  
            //    field.Attributes = (field.Attributes & ~MemberAttributes.AccessMask & ~MemberAttributes.ScopeMask) | MemberAttributes.Public | MemberAttributes.Const;

            //    if (fieldType == "System.String")
            //    {
            //        field.InitExpression = new CodePrimitiveExpression(fieldValue);
            //    }
            //    else
            //    {
            //        field.InitExpression = new CodeSnippetExpression(fieldValue);
            //    }
            //}
            //else
            //{
                switch (fieldAccess)
                {
                    case "public":
                        field.Attributes = MemberAttributes.Public;
                        break;
                    case "protected":
                        field.Attributes = MemberAttributes.Family;
                        break;
                    case "private":
                        field.Attributes = MemberAttributes.Private;
                        break;
                    default:
                        throw new ArgumentException(string.Format(Strings.BadVariableAccess, fieldAccess));
                }

                //if (fieldIsStatic)
                //{
                //    field.Attributes |= MemberAttributes.Static;
                //}
            //}

            return field;
        }

        /// <summary>
        /// Add three properties to the class.
        /// </summary>
        private static CodeMemberProperty CreateProperties(string propertyName, string fieldName, Type dataType, bool direction)
        {
            // Declare the read-only Width property.
            CodeMemberProperty attributeProperty = new CodeMemberProperty();
            attributeProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final;
            attributeProperty.Name = propertyName;
            attributeProperty.HasGet = true;
            attributeProperty.HasSet = true;
            attributeProperty.Type = new CodeTypeReference(dataType);

            attributeProperty.GetStatements.Add(new CodeMethodReturnStatement(
                new CodeFieldReferenceExpression(
                new CodeThisReferenceExpression(), fieldName)));


            attributeProperty.SetStatements.Add(new CodeAssignStatement(
                new CodeFieldReferenceExpression(
                    new CodeThisReferenceExpression(), fieldName), new CodePropertySetValueReferenceExpression()));

            if (!direction)
            {
                attributeProperty.CustomAttributes.Add(
                          new CodeAttributeDeclaration("Direction",
                          new CodeAttributeArgument("Output", new CodePrimitiveExpression(Convert.ToBoolean("false")))));

            }
            else
            {
                attributeProperty.CustomAttributes.Add(
                          new CodeAttributeDeclaration("Direction",
                          new CodeAttributeArgument("Output", new CodePrimitiveExpression(Convert.ToBoolean("true")))));
            }

            return attributeProperty;
            //targetClass.Members.Add(attributeProperty);
        }

        /// <summary>
        /// Adds a method to the class. This method multiplies values stored 
        /// in both fields.
        /// </summary>
        private static CodeMemberMethod CreateMethod(IList<string> propNameList, string strclassName, string projType)
        {
            // Declaring a ToString method
            CodeMemberMethod toCompareMethod = new CodeMemberMethod();
            toCompareMethod.Attributes =
                MemberAttributes.Public | MemberAttributes.Final;
            toCompareMethod.Name = "Compare";
            toCompareMethod.ReturnType =
                new CodeTypeReference(typeof(int));

            CodeParameterDeclarationExpression param1 = new CodeParameterDeclarationExpression(typeof(object), "x");
            CodeParameterDeclarationExpression param2 = new CodeParameterDeclarationExpression(typeof(object), "y");

            toCompareMethod.Parameters.Add(param1);
            toCompareMethod.Parameters.Add(param2);

            StringBuilder sbCodeConditionExpr = new StringBuilder();

            foreach (string item in propNameList)
            {
                IList<string> propList = item.Split('|');

                if (!propList[2].ToUpper().Equals("OUTPUT"))
                {
                    sbCodeConditionExpr.Append("(");
                    sbCodeConditionExpr.Append(propList[0]);
                    sbCodeConditionExpr.Append(".Equals(((");
                    sbCodeConditionExpr.Append(strclassName);
                    sbCodeConditionExpr.Append(")y).");
                    sbCodeConditionExpr.Append(propList[0]);
                    sbCodeConditionExpr.Append("))");
                    sbCodeConditionExpr.Append(" && ");
                }
            }

            sbCodeConditionExpr = sbCodeConditionExpr.Remove(sbCodeConditionExpr.Length - 4, 4);
            CodeConditionStatement codeCondStatement = null;

            if (projType.Equals(".csproj"))
            {
                codeCondStatement = new CodeConditionStatement(
                        new CodeSnippetExpression(sbCodeConditionExpr.ToString()),
                        new CodeStatement[] { new CodeSnippetStatement("return 1;") },
                        new CodeStatement[] { new CodeSnippetStatement("return 0;") });
            }
            else if (projType.Equals(".vbproj"))
            {
                codeCondStatement = new CodeConditionStatement(
                        new CodeSnippetExpression(sbCodeConditionExpr.ToString()),
                        new CodeStatement[] { new CodeSnippetStatement("return 1") },
                        new CodeStatement[] { new CodeSnippetStatement("return 0") });
            }
            else if (projType.Equals(".jsproj"))
            {
                codeCondStatement = new CodeConditionStatement(
                        new CodeSnippetExpression(sbCodeConditionExpr.ToString()),
                        new CodeStatement[] { new CodeSnippetStatement("return 1;") },
                        new CodeStatement[] { new CodeSnippetStatement("return 0;") });
            }

            toCompareMethod.Statements.Add(codeCondStatement);


            return toCompareMethod;

            //targetClass.Members.Add(toCompareMethod);
        }

        

        private static void GetFieldInfo(XmlNode n, out string memberName, out string memberAccess, out string memberValue, out string memberType, out bool memberIsStatic)
        {
            if (n.Name != "Variable" && n.Name != "Constant")
            {
                throw new ArgumentException(string.Format(Strings.BadClassMemberName, n.Name));
            }

            if (n.Attributes != null && n.Attributes.GetNamedItem("name") != null && n.Attributes.GetNamedItem("name").Value != string.Empty)
            {
                memberName = n.Attributes.GetNamedItem("name").Value;
            }
            else
            {
                throw new ArgumentException(Strings.ClassMemberNoName);
            }

            if (n.Attributes.GetNamedItem("type") != null)
            {
                memberType = n.Attributes.GetNamedItem("type").Value;
            }
            else
            {
                throw new ArgumentException(Strings.ClassMemberNoType);
            }

            if (n.Attributes.GetNamedItem("value") != null)
            {
                memberValue = n.Attributes.GetNamedItem("value").Value;
            }
            else
            {
                if (n.Name == "Constant")
                {
                    throw new ArgumentException(Strings.ConstantNoValue);
                }
                else
                {
                    memberValue = null;
                }
            }

            if (n.Attributes.GetNamedItem("access") != null)
            {
                memberAccess = n.Attributes.GetNamedItem("access").Value;
            }
            else
            {
                memberAccess = "public";
            }

            if (n.Attributes.GetNamedItem("static") != null)
            {
                if (n.Name == "Constant")
                {
                    throw new ArgumentException(Strings.ConstantNoValue);
                }
                else
                {
                    memberIsStatic = bool.Parse(n.Attributes.GetNamedItem("static").Value);
                }
            }
            else
            {
                memberIsStatic = false;
            }
        }

        private static CodeTypeDeclaration CreateEnum(XmlNode node)
        {
            string enumName;
            string enumAccess;
            bool enumFlagsAttribute;

            GetEnumInfo(node, out enumName, out enumAccess, out enumFlagsAttribute);
            
            CodeTypeDeclaration typeDeclaration = new CodeTypeDeclaration(enumName);
            typeDeclaration.IsEnum = true;

            if (enumFlagsAttribute)
            {
                typeDeclaration.CustomAttributes.Add(new CodeAttributeDeclaration("System.FlagsAttribute"));
            }

            TypeAttributes attrib;
            switch (enumAccess)
            {
                case "public":
                    attrib = TypeAttributes.Public;
                    break;
                case "internal":
                    attrib = TypeAttributes.NotPublic;
                    break;
                default:
                    throw new ArgumentException(string.Format(Strings.BadTypeAccess, enumAccess));
            }

            typeDeclaration.TypeAttributes = attrib;

            foreach (XmlNode n in node.ChildNodes)
            {
                string memberName;
                int memberValue;
                bool hasValue;

                GetEnumMemberInfo(enumFlagsAttribute, n, out memberName, out memberValue, out hasValue);

                CodeMemberField field = new CodeMemberField("System.Int32", memberName);
                if (hasValue)
                {
                    field.InitExpression = new CodePrimitiveExpression(memberValue);
                }

                typeDeclaration.Members.Add(field);                
            }

            return typeDeclaration;
        }

        private static void GetEnumMemberInfo(bool enumFlagsAttribute, XmlNode n, out string memberName, out int memberValue, out bool hasValue)
        {
            if (n.Name != "EnumMember")
            {
                throw new ArgumentException(string.Format(Strings.EnumNodeNoName, n.Name));
            }

            if (n.Attributes != null && n.Attributes.GetNamedItem("name") != null && n.Attributes.GetNamedItem("name").Value != string.Empty)
            {
                memberName = n.Attributes.GetNamedItem("name").Value;
            }
            else
            {
                throw new ArgumentException(Strings.EnumMemberNodeNoName);
            }

            if (n.Attributes.GetNamedItem("value") != null)
            {
                memberValue = System.Int32.Parse(n.Attributes.GetNamedItem("value").Value);
                hasValue = true;
            }
            else
            {
                if (enumFlagsAttribute)
                {
                    throw new ArgumentException(Strings.EnumMemberValueMissing);
                }
                else
                {
                    memberValue = 0;
                    hasValue = false;
                }
            }
        }

        private static void GetEnumInfo(XmlNode node, out string enumName, out string enumAccess, out bool enumFlagsAttribute)
        {
            if (node.Attributes != null && node.Attributes.GetNamedItem("name") != null && node.Attributes.GetNamedItem("name").Value != string.Empty)
            {
                enumName = node.Attributes.GetNamedItem("name").Value;
            }
            else
            {
                throw new ArgumentException(Strings.EnumNodeNoName);
            }
            
            if (node.ChildNodes.Count == 0)
            {
                throw new ArgumentException(Strings.EnumNoMembers);
            }

            if (node.Attributes.GetNamedItem("access") != null)
            {
                enumAccess = node.Attributes.GetNamedItem("access").Value;
            }
            else
            {
                enumAccess = "public";
            }

            if (node.Attributes.GetNamedItem("flagsAttribute") != null)
            {
                enumFlagsAttribute = bool.Parse(node.Attributes.GetNamedItem("flagsAttribute").Value);
            }
            else
            {
                enumFlagsAttribute = false;
            }
        }
    }
}